package com.zjb.demo.system.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.zjb.demo.common.constant.CacheConstants;
import com.zjb.demo.common.constant.TokenConstants;
import com.zjb.demo.common.domain.LoginUser;
import com.zjb.demo.common.util.JwtUtils;
import com.zjb.demo.system.domain.vo.TokenView;
import org.apache.commons.lang3.StringUtils;
import org.noear.redisx.plus.RedisBucket;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.Context;

import java.util.HashMap;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * @author 庄佳彬
 * @since 2023/12/28 18:01
 */
@Component
public class TokenService {
    protected static final int MILLIS_SECOND = 1000;
    protected static final int MILLIS_MINUTE = 60 * MILLIS_SECOND;
    private static final long MILLIS_MINUTE_TEN = 20L * MILLIS_MINUTE;
    private static final int expireTime = 30;

    @Inject
    private RedisBucket redisBucket;

    /**
     * 创建TokenView对象
     *
     * @param loginUser   登录用户对象
     * @param mobileLogin 是否是移动端登录
     * @return TokenView对象
     */
    public TokenView createToken(LoginUser loginUser, String mobileLogin) {
        // 生成Token
        var token = IdUtil.fastUUID();
        // 生成RefreshToken
        var refreshToken = IdUtil.fastUUID();
        // 获取用户ID
        var userId = loginUser.getUser().getId();
        // 获取用户名
        var userName = loginUser.getUserName();

        // 更新登录用户对象的信息
        loginUser.setToken(token);
        loginUser.setRefreshToken(refreshToken);
        loginUser.setUserId(userId);
        loginUser.setUsername(userName);
        loginUser.setIpaddr(Context.current().realIp());

        // 刷新Token
        this.refreshToken(loginUser, mobileLogin);

        // 创建声明对象
        HashMap<String, Object> claimsMap = new HashMap<>();
        claimsMap.put("user_key", token);
        claimsMap.put("user_id", userId);
        claimsMap.put("username", userName);

        // 创建TokenView对象并返回
        return new TokenView(JwtUtils.createToken(claimsMap), refreshToken, 720L);
    }


    private void refreshToken(LoginUser loginUser, String mobileLogin) {
        refreshToken(loginUser);
        if ("miniProgram".equalsIgnoreCase(mobileLogin)) {
            refreshMiniProgramToken(loginUser.getUser().getId(), loginUser.getToken());
        }

    }

    private void refreshMiniProgramToken(Long userId, String token) {
        if (redisBucket.exists("mini_program_user_id:" + userId)) {
            var oldToken = redisBucket.get("mini_program_user_id:" + userId);
            redisBucket.remove("login_tokens:" + oldToken);
        }
        redisBucket.store("mini_program_user_id:" + userId, token, 720 * 60);
    }

    private void refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + (long) expireTime * MILLIS_MINUTE);
        var userKey = this.getTokenKey(loginUser.getToken());
        redisBucket.storeAndSerialize(userKey, loginUser, expireTime * 60);
        var refreshKey = this.getRefreshTokenKey(loginUser.getRefreshToken());
        redisBucket.storeAndSerialize(refreshKey, loginUser, expireTime * 2 * MILLIS_MINUTE);
    }

    /**
     * 根据token获取登录用户
     *
     * @return LoginUser类型的登录用户对象
     */
    public Optional<LoginUser> getLoginUserByToken() {
        // 获取token
        var token = Context.current().header(TokenConstants.AUTHENTICATION);
        token = replaceTokenPrefix(token);
        if (StrUtil.isEmpty(token)) {
            return Optional.empty();
        }
        // 获取用户key
        var userKey = JwtUtils.getUserKey(token);
        // 根据用户key从redis中获取并反序列化登录用户对象
        return Optional.ofNullable(redisBucket.getAndDeserialize(getTokenKey(userKey), LoginUser.class));
    }

    private static String replaceTokenPrefix(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (!StrUtil.isEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, "");
        }
        return token;
    }


    private String getRefreshTokenKey(String uuid) {
        return CacheConstants.LOGIN_REFRESH_KEY + uuid;
    }

    private String getTokenKey(String uuid) {
        return CacheConstants.LOGIN_TOKEN_KEY + uuid;
    }


    /**
     * 验证token是否有效
     *
     * @param loginUser 登录用户对象
     * @return 若token有效返回true，否则返回false
     */
    public boolean verifyToken(Supplier<Optional<LoginUser>> loginUser) {
        var lu = loginUser.get();
        return lu.map(LoginUser::getExpireTime)
                .map(expireTime -> {
                    // 获取当前时间
                    long currentTime = System.currentTimeMillis();
                    if (expireTime - currentTime > 0) {
                        // 重新刷新token
                        refreshToken(lu.get());
                        return true;
                    } else {
                        return false;
                    }
                })
                .orElse(false);
    }


    /**
     * 根据刷新令牌获取用户登录信息
     *
     * @return 用户登录信息
     */
    public Optional<LoginUser> getLoginUserByRefreshToken() {
        // 获取刷新令牌
        var refreshToken = Context.current().header(TokenConstants.REFRESH_TOKEN);
        if (StrUtil.isEmpty(refreshToken)) {
            return Optional.empty();
        }
        // 获取刷新令牌对应的用户密钥
        var refreshTokenKey = getRefreshTokenKey(refreshToken);
        // 从Redis缓存中获取并反序列化用户登录信息
        var loginUser = redisBucket.getAndDeserialize(refreshTokenKey, LoginUser.class);
        // 移除刷新令牌对应的用户登录信息
        redisBucket.remove(refreshTokenKey);
        // 返回用户登录信息
        return Optional.ofNullable(loginUser);
    }

    /**
     * 设置登录用户
     *
     * @param loginUser 登录用户
     */
    public void setLoginUser(Supplier<Optional<LoginUser>> loginUser) {
        loginUser.get()
                .filter(it -> StringUtils.isNotEmpty(it.getToken()))
                .ifPresent(this::refreshToken);
    }

    /**
     * 根据token删除登录用户
     *
     * @param token 用户token
     */
    public void delLoginUser(Supplier<Optional<String>> token) {
        token.get().ifPresent(t -> {
            // 获取用户key
            String userKey = this.getTokenKey(t);
            // 从redis中删除用户key
            redisBucket.remove(userKey);

            // 获取刷新token的key
            String refreshKey = this.getRefreshTokenKey(t);
            // 从redis中删除刷新token的key
            redisBucket.remove(refreshKey);
        });
    }

}
